﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using Dsl.Ped.Common;
using Dsl.Ped.Common.Exceptions;
using Dsl.Ped.Common.Helpers;
using Dsl.Ped.Framework.Exceptions;

namespace Dsl.Ped.Framework.Common
{
    /// <summary>
    /// General implemenentation of <see cref="IProjectionContainer"/> interface
    /// </summary>
    public class ProjectionContainer : ViewModelBase, IProjectionContainer, INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// Default projection of the container
        /// </summary>
        private IProjection defaultProjection = null;

        /// <summary>
        /// Dictionary of the projections in the container
        /// </summary>
        private Dictionary<string, IProjection> projectionsDictionary = null;

        /// <summary>
        /// Repository of transitions in the container
        /// </summary>
        private List<Transition> transitionsRepository = null;

        /// <summary>
        /// Repository of projections in the container
        /// </summary>
        private List<IProjection> projectionsRepository = null;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ProjectionContainer"/> class. 
        /// </summary>
        /// <param name="name">Name of the container</param>
        /// <param name="projection">First projection in the container</param>
        public ProjectionContainer(
            string name,
            IProjection projection)
        {
            this.Name = name;
            this.ForceRefresh = false;

            this.RegisterProjection(string.Empty, projection);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ProjectionContainer"/> class.
        /// </summary>
        /// <param name="name">The name of a container</param>
        public ProjectionContainer(string name)
            : this(name, null)
        {
        }

        #endregion

        #region Members

        /// <summary>
        /// Name of the control
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Default projection of the container
        /// </summary>
        public IProjection DefaultProjection 
        {
            get
            {
                return defaultProjection;
            }
            private set {
                defaultProjection = value;
                RaisePropertyChanged(() => DefaultProjection);
            }
        }

        /// <summary>
        /// Force the refresh of the container
        /// </summary>
        public bool ForceRefresh { get; set; }

        /// <summary>
        /// Gets the transitions in the container
        /// </summary>
        public ReadOnlyCollection<Transition> Transitions
        {
            get
            {
                return new ReadOnlyCollection<Transition>(TransitionsRepository);
            }
        }

        /// <summary>
        /// Gets the projections in the container
        /// </summary>
        public ReadOnlyCollection<IProjection> Projections
        {
            get
            {
                return new ReadOnlyCollection<IProjection>(ProjectionsRepository);
            }
        }

        /// <summary>
        /// Gets the image representing the container
        /// </summary>
        public ImageSource Image
        {
            get { return null; }
        }

        /// <summary>
        /// Gets the dictionary of projection (name to <see cref="IProjection"/> dictionary)
        /// </summary>
        private Dictionary<string, IProjection> ProjectionsDictionary {
            get
            {
                return projectionsDictionary ?? (projectionsDictionary = new Dictionary<string, IProjection>());
            }
        }

        /// <summary>
        /// Gets the repository of projections
        /// </summary>
        private List<IProjection> ProjectionsRepository
        {
            get
            {
                return projectionsRepository ?? (projectionsRepository = new List<IProjection>());
            }
        }

        /// <summary>
        /// Gets the repository of transitions
        /// </summary>
        private List<Transition> TransitionsRepository
        {
            get 
            { 
                return transitionsRepository ?? (transitionsRepository = new List<Transition>());
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Register new projection under a specific name
        /// </summary>
        /// <param name="projectionName">Name of the projection that is registered</param>
        /// <param name="projection"><see cref="IProjection"/> that is being registered</param>
        /// <returns><see cref="IProjectionContainer"/> in which the projection is registered</returns>
        public IProjectionContainer RegisterProjection(string projectionName, IProjection projection)
        {
            // Don't register empty projection
            if (projection == null) { return this; }

            // Try to assign a name to the projection,
            // if the name is not valid XAML name, throws an exception
            try
            {
                projection.Name = XamlHelper.CheckValidXamlName(projectionName);
            }
            catch (InvalidStringException exc)
            {
                throw new ProjectionInvalidNameRegisteredException(projection);
            }

            // Initialize default projection
            if (this.DefaultProjection == null) DefaultProjection = projection;

            // Try to add the projection to the dictionary under the given name
            // Throws an exception if a projection is already registered under the same name
            try
            {
                this.ProjectionsDictionary.Add(projectionName, projection);
                this.ProjectionsRepository.Add(projection);
            }
            catch (ArgumentException)
            {
                throw new ProjectionAlreadyRegisteredException(this, projectionName);
            }

            return this;
        }

        /// <summary>
        /// Get the projection of a given name
        /// </summary>
        /// <param name="projectionName">Required name of the projection</param>
        /// <returns>Projection of the given name</returns>
        public IProjection GetProjection(string projectionName)
        {
            IProjection projectionItem = null;
            ProjectionsDictionary.TryGetValue(projectionName, out projectionItem);

            return projectionItem;
        }

        /// <summary>
        /// Register the transition between two given projections specified by the names.
        /// The transition is routed by the <see cref="RoutedEvent"/>
        /// </summary>
        /// <param name="routedEvent">Event that cause the transition</param>
        /// <param name="sourceProjection">Source projection name</param>
        /// <param name="targetProjection">Target projection name</param>
        /// <returns>Container that the transition belongs to</returns>
        public IProjectionContainer RegisterTransition(RoutedEvent routedEvent, string sourceProjection, string targetProjection)
        {
            if (sourceProjection == targetProjection)
            {
                throw new Exception("Deadlock detected, transition of projection targets to itself");
            }

            this.TransitionsRepository.Add(new Transition(routedEvent, sourceProjection, targetProjection));
            return this;
        }

        #endregion
    }
}
